#version 330

in vec2 ex_TexCoord;
in vec3 ex_PosW;
in vec3 ex_NormalW;

out vec4 out_Color;

// 基础光照模型
struct BaseLightModel
{
	vec4 ambient;	// 环境光
	vec4 diffuse;	// 漫射光
	vec4 specular;	// 镜面高光
	bool state;		// 开启/关闭
};

// 方向光模型
struct DirectionLightModel
{
	BaseLightModel baseAttr;
	vec3 dir;
};

// 点光源模型
struct PointLightModel
{
	BaseLightModel baseAttr;
	vec3 position;
	vec3 atten;
	float range;
};

// 聚光灯模型
struct SpotLightModel
{
	BaseLightModel baseAttr;
	float cutoff;
	float pf;
	vec3 atten;
	vec3 dir;
	vec3 position;
};

struct Material
{
	vec4 emissive; 		// 辐射度
	vec4 ambient;		// 环境光
	vec4 diffuse;		// 漫射光
	vec4 specular;		// 镜面高光
	float shininess; 	// 反射指数
};


uniform sampler2D texSampler;
uniform vec3 eyePosW;
uniform vec4 g_Ambient;

uniform DirectionLightModel dirLight;
uniform PointLightModel pointLight;
uniform SpotLightModel spotLight;

uniform Material material;

// 黑色
const vec4 black = vec4(0.0f, 0.0f, 0.0f, 1.0f);

void computerDirectionLight(DirectionLightModel light, Material mat, vec3 normal, vec3 toEye, out vec4 ambient, out vec4 diffuse, out vec4 specular)
{
	ambient = diffuse = specular = black;

	// 光照方向
	vec3 lightVec = -light.dir;
	
	// 计算出环境光
	ambient = mat.ambient * light.baseAttr.ambient;

	// 计算漫射光
	float NdotL = max(dot(normal, lightVec), 0.0f);
	diffuse = NdotL * mat.diffuse * light.baseAttr.diffuse;

	// 计算镜面高光
	vec3 halfVec = normalize(lightVec + toEye);
	vec3 R = normalize(reflect(-lightVec, normal));
	float RdotE = max(dot(R, toEye), 0.0f);			// Phong光照模型
	float NdotH = max(dot(normal, halfVec), 0.0f);	// Blinn-Phong光照模型
	specular = pow(RdotE, mat.shininess) * mat.specular * light.baseAttr.specular;
}

void computerPointLignt(PointLightModel light, Material mat, vec3 pos, vec3 normal, vec3 toEye, out vec4 ambient, out vec4 diffuse, out vec4 specular)
{
	ambient = diffuse = specular = black;

	// 计算光照方向
	vec3 lightVec = light.position - pos;

	// 计算被照射表面与光源之间的距离。若超出光照范围将没有光照
	float d = length(lightVec);

	// 范围检查
	if (d > light.range)
	 	return;

	// 归一化光照方向向量
	lightVec = normalize(lightVec);

	// 衰减公式
	float atten = 1.0f / dot(light.atten, vec3(1.0f, d, d * d));

	// 首先，计算环境光
	ambient = mat.ambient * light.baseAttr.ambient;

	// 接着，计算漫射光和镜面高光

	// 漫射光
	float NdotL = max(dot(normal, lightVec), 0.0f);
	diffuse = NdotL * mat.diffuse * light.baseAttr.diffuse;

	// 镜面高光
	vec3 halfVec = normalize(lightVec + toEye);
	vec3 R = reflect(-lightVec, normal);
	float RdotE = max(dot(R, toEye), 0.0f); 		// Phong光照模型
	float NdotH = max(dot(normal, halfVec), 0.0f); 	// Blinn-Phong光照模型
	specular = pow(RdotE, mat.shininess) * mat.specular * light.baseAttr.specular;
}

void computerSpotLight(SpotLightModel light, Material mat, vec3 pos, vec3 normal, vec3 toEye, out vec4 ambient, out vec4 diffuse, out vec4 specular)
{
	ambient = diffuse = specular = black;

	// 计算光源到表面向量
	vec3 toSurface = light.position - pos;

	// 顶点与光源的距离
	float d = length(toSurface);
	
	// 衰减公式
	float atten = 1.0f / dot(light.atten, vec3(1.0f, d, d * d));

	// 归一化向量
	toSurface = toSurface / d;
	float angle = acos(dot(-toSurface, light.dir));
	float cutoff = radians( clamp(light.cutoff, 0.0, 90.0) );

	// 首先, 计算环境光
	ambient = mat.ambient * light.baseAttr.ambient;

	if (angle < cutoff)
	{	
		float attenFactor = pow(dot(-toSurface, light.dir), light.pf);

		// 接着, 计算漫射光
		float NdotL = max(dot(normal, toSurface), 0.0f);
		diffuse = attenFactor * NdotL * (mat.diffuse * light.baseAttr.diffuse) * atten;

		// 最后, 计算镜面高光
		vec3 halfVec = normalize(toSurface + toEye);
		vec3 R = reflect(-toSurface, normal);
		float RdotE = max(dot(R, toEye), 0.0f);			// Phong光照模型
		float NdotH = max(dot(normal, halfVec), 0.0f);	// Blinn-Phong光照模型
		specular = attenFactor * (pow(RdotE, mat.shininess) * mat.specular * light.baseAttr.specular) * atten;
	}
}

void main(void)
{
	vec4 ambient, diffuse, specular;
	ambient = diffuse = specular = black;

	vec4 emissive = material.emissive;

	vec3 toEyeW = normalize(eyePosW - ex_PosW);
	vec3 normalW = normalize(ex_NormalW);

	vec4 A, D, S;
	if (dirLight.baseAttr.state == true)
	{
		computerDirectionLight(dirLight, material, normalW, toEyeW, A, D, S);
		ambient += A;
		diffuse += D;
		specular += S;
	}

	if (pointLight.baseAttr.state == true)
	{
		computerPointLignt(pointLight, material, ex_PosW, normalW, toEyeW, A, D, S);
		ambient += A;
		diffuse += D;
		specular += S;
	}

	if (spotLight.baseAttr.state == true)
	{
		computerSpotLight(spotLight, material, ex_PosW, normalW, toEyeW, A, D, S);
		ambient += A;
		diffuse += D;
		specular += S;
	}

	vec4 texColor = texture(texSampler, ex_TexCoord);
	out_Color = texColor; //(emissive + g_Ambient + ambient + diffuse) * texColor + specular;
	out_Color.a = material.diffuse.a * texColor.a;
}



